/*
 author: Naim Falandino naim@knowlogik.com
 description: Native interface to Pd for Android via JNI. Based on native
 interface to Pd from Reality Jockey (see ORIGINAL COPYRIGHT below).

 ORIGINAL COPYRIGHT
 This software is copyrighted by Reality Jockey, Ltd..  The following
 terms (the "Standard Improved BSD License") apply to all files associated with
 the software unless explicitly disclaimed in individual files:
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are
 met:
 
 1. Redistributions of source code must retain the above copyright
 notice, this list of conditions and the following disclaimer.
 2. Redistributions in binary form must reproduce the above  
 copyright notice, this list of conditions and the following 
 disclaimer in the documentation and/or other materials provided
 with the distribution.
 3. The name of the author may not be used to endorse or promote
 products derived from this software without specific prior 
 written permission.
 
 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
 EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR
 BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,   
 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifdef ANDROID

#include <jni.h>
#include <android/log.h>
#include <stdio.h>
#include <stdlib.h>

#include "s_audio_android.h"
#include "m_pd.h"
#include "s_stuff.h"

#define JNI_VERSION JNI_VERSION_1_4
#define PD_BLOCKSIZE DEFDACBLKSIZE
#define NUM_CHANNELS 2 // Stereo
#define LOG_TAG "s_audio_android"

static JavaVM *jvm;
static jobject objAudioRecord;
static jobject objAudioTrack;
static int audioBufferSize;

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* ur_jvm, void* reserved) {

	jvm = ur_jvm;

	objAudioRecord = NULL;
	objAudioTrack = NULL;

	return JNI_VERSION; // require at least JNI version 1.4
}

JNIEXPORT void JNICALL Java_org_puredata_PdAudio_setNativeAudioReference(
		JNIEnv* env, jobject jobj, jobject audioTrack) {

	objAudioTrack = (*env)->NewGlobalRef(env, audioTrack);
}

JNIEXPORT void JNICALL Java_org_puredata_PdAudio_deleteNativeAudioReference(
		JNIEnv* env, jobject jobj, jobject audioTrack) {

	if (objAudioTrack != NULL) {
		(*env)->DeleteGlobalRef(env, objAudioTrack);
		objAudioTrack = NULL;
	}
}

JNIEXPORT void JNICALL Java_org_puredata_PdAudio_setNativeAudioReferences(
		JNIEnv* env, jobject jobj, jobject audioTrack, jobject audioRecord) {

	objAudioTrack = (*env)->NewGlobalRef(env, audioTrack);
	objAudioRecord = (*env)->NewGlobalRef(env, audioRecord);
}

JNIEXPORT void JNICALL Java_org_puredata_PdAudio_deleteNativeAudioReferences(
		JNIEnv* env, jobject jobj, jobject audioTrack, jobject audioRecord) {

	if (objAudioTrack != NULL) {
		(*env)->DeleteGlobalRef(env, objAudioTrack);
		objAudioTrack = NULL;
	}

	if (objAudioRecord != NULL) {
		(*env)->DeleteGlobalRef(env, objAudioRecord);
		objAudioRecord = NULL;
	}
}

JNIEXPORT void JNICALL Java_org_puredata_PdService_openPatch(
		JNIEnv* env, jobject jobj, jstring jfilename, jstring jdirectory) {

	const char *cfilename = (char *) (*env)->GetStringUTFChars(env, jfilename, NULL);
	const char *cdirectory = (char *) (*env)->GetStringUTFChars(env, jdirectory, NULL);

	pd_openpatch(cfilename, cdirectory);

	(*env)->ReleaseStringUTFChars(env, jfilename, cfilename);
	(*env)->ReleaseStringUTFChars(env, jdirectory, cdirectory);
}

JNIEXPORT void JNICALL Java_org_puredata_PdService_startPd(JNIEnv* env, jobject jobj) {

	//startpd(NULL); // start without thread
	pd_start(); // start with thread
}

JNIEXPORT void JNICALL Java_org_puredata_PdService_sendMessage(
		JNIEnv* env, jobject jobj, jstring jreceive, jstring jmessage, jstring jformat, jint i) {

	const char *creceive = (char *) (*env)->GetStringUTFChars(env, jreceive, NULL);
	const char *cmessage = (char *) (*env)->GetStringUTFChars(env, jmessage, NULL);
	const char *cformat = (char *) (*env)->GetStringUTFChars(env, jformat, NULL);

	pd_send(creceive, cmessage, cformat, i);

	(*env)->ReleaseStringUTFChars(env, jreceive, creceive);
	(*env)->ReleaseStringUTFChars(env, jmessage, cmessage);
	(*env)->ReleaseStringUTFChars(env, jformat, cformat);
}

/**
 * NOTE (naim) This isn't currently used.
 * Called from PD in order to open the audio system
 */
int android_open_audio(int inchans, int outchans, int samplerate) {

	// TODO(mhroth): nothing to do?
	return 0; // no error
}

/**
 * NOTE (naim) This isn't currently used.
 * Called from PD in order to close and shutdown the audio system
 */
int android_close_audio() {

	// TODO(mhroth): nothing to do?
	return 0; // no error
}

/**
 * Called from PD in order to get audio input and return audio output.
 * PD is sending audio to the DACs. This is the audio thread of PD.
 * We also use it as the audio thread for Android. This thread calls 
 * the Android audio routines.
 * NOTE: this is where all of the action is :-P
 */
int android_send_dacs() {

	static double timenow, timeref = 0.0;
	//timeref = sys_getrealtime();

	JNIEnv *env = NULL;
	jint error = (*jvm)->AttachCurrentThread(jvm, &env, NULL);

	if (error == JNI_OK && env != NULL) {
		// PD audio thread successfully attached; everything is cool... continue as normal

		static int i, j, k; // iteration indicies
		static int bufferSizeInShorts;
		static int bufferIndex;
		static double bufferSizeInSeconds;
		static jshortArray jinputBuffer, joutputBuffer = NULL;

		static jmethodID methodIdAudioTrackWrite;
		static jmethodID methodIdAudioRecordRead;

		if (joutputBuffer == NULL) {
			bufferSizeInShorts = 32768; // TODO(mhroth): fix this
			bufferIndex = 0;
			jinputBuffer = (*env)->NewGlobalRef(env, (*env)->NewShortArray(env, bufferSizeInShorts));
			joutputBuffer = (*env)->NewGlobalRef(env, (*env)->NewShortArray(env, bufferSizeInShorts));

			methodIdAudioTrackWrite = (*env)->GetMethodID(
					env, (*env)->FindClass(env, "android/media/AudioTrack"), "write", "([SII)I");
			if (objAudioRecord != NULL) {
				methodIdAudioRecordRead = (*env)->GetMethodID(
						env, (*env)->FindClass(env, "android/media/AudioRecord"), "read", "([SII)I");
			}

			bufferSizeInSeconds = ((double) PD_BLOCKSIZE) / ((double) sys_dacsr);
			//__android_log_print(ANDROID_LOG_WARN, LOG_TAG, "Buffer size is %f seconds.", bufferSizeInSeconds);
			//__android_log_print(ANDROID_LOG_WARN, LOG_TAG, "sys_schedblocksize == %i", sys_schedblocksize);
		}

		short *coutputBuffer = (short *) (*env)->GetPrimitiveArrayCritical(env, joutputBuffer, NULL);
		for (i = 0, j = PD_BLOCKSIZE, k = bufferIndex; i < PD_BLOCKSIZE; i++, j++) {
			coutputBuffer[k++] = (short) (sys_soundout[i] * 32767.0f);
			coutputBuffer[k++] = (short) (sys_soundout[j] * 32767.0f);
		}
		(*env)->ReleasePrimitiveArrayCritical(env, joutputBuffer, coutputBuffer, 0); // copy back the results

		if (objAudioRecord != NULL) {
			short *cinputBuffer = (short *) (*env)->GetPrimitiveArrayCritical(env, jinputBuffer, NULL);
			for (i = 0, j = PD_BLOCKSIZE, k = bufferIndex; i < PD_BLOCKSIZE; i++, j++) {
				sys_soundin[i] = ((float) cinputBuffer[k++]) / 32767.0f;
				sys_soundin[j] = ((float) cinputBuffer[k++]) / 32767.0f;
			}
			(*env)->ReleasePrimitiveArrayCritical(env, jinputBuffer, cinputBuffer, JNI_ABORT); // no need to copy back changes
		}

		// reset sys_soundout buffers because dac~ adds into it
		memset(sys_soundout, 0, PD_BLOCKSIZE * NUM_CHANNELS * sizeof(t_sample));

		bufferIndex += PD_BLOCKSIZE << 1; // *2 because java buffers use interleaved samples

		if (bufferIndex == bufferSizeInShorts) {
			__android_log_print(
					ANDROID_LOG_VERBOSE, LOG_TAG,
					"android_send_dacs buffer fill took %f seconds.", sys_getrealtime()-timeref);

			// send the audio to the Android audio out; call AudioTrack.write(short[])
			(*env)->CallIntMethod(
					env,
					objAudioTrack,
					methodIdAudioTrackWrite,
					joutputBuffer, 0, bufferSizeInShorts);

			if (objAudioRecord != NULL) {
				// read the audio from the Android audio in; call AudioRecord.read(short[])
				(*env)->CallIntMethod(
						env,
						objAudioRecord,
						methodIdAudioRecordRead,
						jinputBuffer, 0, bufferSizeInShorts);
			}

			bufferIndex = 0;

			__android_log_print(
					ANDROID_LOG_VERBOSE, LOG_TAG,
					"android_send_dacs buffer fill + audio i/o took %f seconds.", sys_getrealtime()-timeref);

			timeref = sys_getrealtime();
		}

		//__android_log_print(ANDROID_LOG_WARN, LOG_TAG, "bufferIndex == %i", bufferIndex);

		// dunno what SENDDACS_SLEPT is for, but all of the other s_audio_XXX do this
		/*
		 if ((timenow = sys_getrealtime()) - timeref > bufferSizeInSeconds) {
		 // there was a buffer underrun
		 __android_log_print(ANDROID_LOG_WARN, LOG_TAG, "android_send_dacs() took %f seconds @ bufferIndex == %i.", timenow-timeref, bufferIndex);
		 return SENDDACS_SLEPT;
		 } else {
		 return SENDDACS_YES;
		 }
		 */
		return SENDDACS_YES;
	} else {
		// the PD audio thread could not be attached to the VM.
		__android_log_write(ANDROID_LOG_WARN, LOG_TAG, "Pd audio thread could not be attached to the JVM.");
		return SENDDACS_NO; // bad news!
	}
}

/**
 * Setup function called from PureData.
 */
void android_getdevs(char *indevlist, int *nindevs, char *outdevlist,
		int *noutdevs, int *canmulti, int maxndev, int devdescsize) {

	int i;
	int numIoDevices = 1;
	*canmulti = 0; // does not support multiple devices

	for (i = 0; i < numIoDevices; i++) {
		sprintf(indevlist + i * devdescsize, "ANDROID");
		sprintf(outdevlist + i * devdescsize, "ANDROID");
	}

	// there is one input and one output device
	*nindevs = numIoDevices;
	*noutdevs = numIoDevices;
}

void android_listdevs() {

	post("device listing not implemented for android yet\n");
}

#endif /* __android__ */
